%code top{

// Copyright (c) ZeroC, Inc.

// NOLINTBEGIN

}

%code requires{

// Included first to get 'TokenContext' which we need to define YYLTYPE before flex does.
#include "GrammarUtil.h"

// I must set the initial stack depth to the maximum stack depth to
// disable bison stack resizing. The bison stack resizing routines use
// simple malloc/alloc/memcpy calls, which do not work for the
// YYSTYPE, since YYSTYPE is a C++ type, with constructor, destructor,
// assignment operator, etc.
#define YYMAXDEPTH  10000      // 10000 should suffice. Bison default is 10000 as maximum.
#define YYINITDEPTH YYMAXDEPTH // Initial depth is set to max depth, for the reasons described above.

// Newer bison versions allow to disable stack resizing by defining yyoverflow.
#define yyoverflow(a, b, c, d, e, f, g, h) yyerror(a)

}

%code top{

// Defines the rule bison uses to reduce token locations. Bison asks that the macro should
// be one-line, and treatable as a single statement when followed by a semi-colon.
// `N` is the number of tokens that are being combined, and (Cur) is their combined location.
#define YYLLOC_DEFAULT(Cur, Rhs, N)                               \
do                                                                \
    if (N == 1)                                                   \
    {                                                             \
        (Cur) = (YYRHSLOC((Rhs), 1));                             \
    }                                                             \
    else                                                          \
    {                                                             \
        if (N)                                                    \
        {                                                         \
            (Cur).firstLine = (YYRHSLOC((Rhs), 1)).firstLine;     \
            (Cur).firstColumn = (YYRHSLOC((Rhs), 1)).firstColumn; \
        }                                                         \
        else                                                      \
        {                                                         \
            (Cur).firstLine = (YYRHSLOC((Rhs), 0)).lastLine;      \
            (Cur).firstColumn = (YYRHSLOC((Rhs), 0)).lastColumn;  \
        }                                                         \
        (Cur).filename = (YYRHSLOC((Rhs), N)).filename;           \
        (Cur).lastLine = (YYRHSLOC((Rhs), N)).lastLine;           \
        (Cur).lastColumn = (YYRHSLOC((Rhs), N)).lastColumn;       \
    }                                                             \
while(0)

}

%code{

// Forward declaration of the lexing function generated by flex, so bison knows about it.
// This must match the definition of 'yylex' (or 'slice_lex') in the generated scanner.
int slice_lex(YYSTYPE* lvalp, YYLTYPE* llocp);

}

%{

#include "Ice/UUID.h"
#include "Parser.h"
#include "GrammarUtil.h"

#include <cstring>
#include <limits>

#ifdef _MSC_VER
// warning C4102: 'yyoverflowlab' : unreferenced label
#    pragma warning(disable:4102)
// warning C4127: conditional expression is constant
#    pragma warning(disable:4127)
// warning C4702: unreachable code
#    pragma warning(disable:4702)
#endif

// Avoid old style cast warnings in generated grammar
#ifdef __GNUC__
#    pragma GCC diagnostic ignored "-Wold-style-cast"
#    pragma GCC diagnostic ignored "-Wunused-label"

// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98753
#    pragma GCC diagnostic ignored "-Wfree-nonheap-object"
#endif

// Avoid clang warnings in generated grammar
#if defined(__clang__)
#    pragma clang diagnostic ignored "-Wconversion"
#    pragma clang diagnostic ignored "-Wsign-conversion"
#    pragma clang diagnostic ignored "-Wunused-but-set-variable"
#    pragma clang diagnostic ignored "-Wunused-label"
#endif

using namespace std;
using namespace Slice;

void
slice_error(const char* s)
{
    // We want 'unit' to emit the error so its error count is incremented.
    currentUnit->error(s);
}

namespace
{
    /// Resolves a `Type` named `name` from the current scope and returns it.
    /// If no such type could be found, this returns `nullptr` instead.
    /// @param name The (possibly scoped) name to resolve.
    /// @param expectInterfaceType `true` if the type ends with '*' (indicated a proxy), false otherwise.
    ///        This function will automatically emit errors for interfaces missing '*', or non-interfaces using '*'.
    [[nodiscard]] TypePtr lookupTypeByName(const string& name, bool expectInterfaceType);

    /// Resolves an `InterfaceDef` named `name` from the current scope and returns it.
    /// If no such interface could be found, this returns `nullptr` instead.
    /// @param name The (possibly scoped) name to resolve.
    [[nodiscard]] InterfaceDefPtr lookupInterfaceByName(const string& name);

    /// Checks if the provided integer token's value is within the range ['0' ... 'int32_t::max'] (inclusive).
    /// If it is within this range, this function will return `true`.
    /// Otherwise this will return `false`, and automatically emit an error stating the issue.
    /// @param token An integer token to check the value of.
    /// @param kindString A string describing what the integer is being used for ("tag", "compact id", etc.).
    ///                   It is only used to emit a more descriptive error message.
    bool checkIntegerBounds(const IntegerTokPtr& token, string_view kindString);
}

%}

// Directs Bison to generate a re-entrant parser.
%define api.pure
// Specifies what type to back the tokens with (their semantic values).
%define api.value.type {Slice::GrammarBasePtr}
// Enables Bison's token location tracking functionality.
%locations
// Specify a custom location type for storing the location & filename of tokens.
%define api.location.type {Slice::TokenContext}

// Tell Bison to emit additional information with its syntax error messages (like which tokens it expected).
%define parse.error detailed

// All keyword tokens. Make sure to modify the "keyword" rule in this
// file if the list of keywords is changed. Also make sure to add the
// keyword to the keyword table in Scanner.l.
%token ICE_MODULE "module keyword"
%token ICE_CLASS "class keyword"
%token ICE_INTERFACE "interface keyword"
%token ICE_EXCEPTION "exception keyword"
%token ICE_STRUCT "struct keyword"
%token ICE_SEQUENCE "sequence keyword"
%token ICE_DICTIONARY "dictionary keyword"
%token ICE_ENUM "enum keyword"
%token ICE_OUT "out keyword"
%token ICE_EXTENDS "extends keyword"
%token ICE_THROWS "throws keyword"
%token ICE_VOID "void keyword"
%token ICE_BOOL "bool keyword"
%token ICE_BYTE "byte keyword"
%token ICE_SHORT "short keyword"
%token ICE_INT "int keyword"
%token ICE_LONG "long keyword"
%token ICE_FLOAT "float keyword"
%token ICE_DOUBLE "double keyword"
%token ICE_STRING "string keyword"
%token ICE_OBJECT "Object keyword"
%token ICE_CONST "const keyword"
%token ICE_FALSE "false keyword"
%token ICE_TRUE "true keyword"
%token ICE_IDEMPOTENT "idempotent keyword"
%token ICE_OPTIONAL "optional keyword"
%token ICE_VALUE "Value keyword"

// Other tokens.
%token ICE_STRING_LITERAL "string literal"
%token ICE_INTEGER_LITERAL "integer literal"
%token ICE_FLOATING_POINT_LITERAL "floating-point literal"
%token ICE_IDENTIFIER "identifier"
%token ICE_SCOPED_IDENTIFIER "scoped identifier"
%token ICE_METADATA_OPEN "["
%token ICE_METADATA_CLOSE "]"
%token ICE_FILE_METADATA_OPEN "[["
%token ICE_FILE_METADATA_CLOSE "]]"

// Here 'OPEN' means these tokens end with an open parenthesis.
%token ICE_IDENT_OPEN "identifier("
%token ICE_KEYWORD_OPEN "keyword("
%token ICE_OPTIONAL_OPEN "optional("

%token BAD_TOKEN "invalid character"

// Precedence rule use to inform the parser when matching '%empty' is preferred over matching a token.
%token EMPTY_PREC
%precedence EMPTY_PREC

// Special handling for when a user makes the following syntax error: '(interface|exception|class) extends...'
// This could be resolved to either a 'bad-identifier' or a 'missing-identifier' depending on context.
// We tell the parser to prefer resolving this as a 'bad-identifier', since it leads to less confusing errors.
%precedence ICE_EXTENDS

%%

// ----------------------------------------------------------------------
start
// ----------------------------------------------------------------------
: definitions
{
}
;

// ----------------------------------------------------------------------
opt_semicolon
// ----------------------------------------------------------------------
: ';'
{
}
| %empty
{
}
;

// ----------------------------------------------------------------------
file_metadata
// ----------------------------------------------------------------------
: ICE_FILE_METADATA_OPEN metadata_list ICE_FILE_METADATA_CLOSE
{
    $$ = $2;
}
| ICE_FILE_METADATA_OPEN error ICE_FILE_METADATA_CLOSE
{
    $$ = make_shared<MetadataListTok>();
}
| ICE_FILE_METADATA_OPEN ICE_FILE_METADATA_CLOSE
{
    currentUnit->warning(WarningCategory::All, "No directives were provided in metadata list");
    $$ = make_shared<MetadataListTok>();
}
;

// ----------------------------------------------------------------------
local_metadata
// ----------------------------------------------------------------------
: ICE_METADATA_OPEN metadata_list ICE_METADATA_CLOSE
{
    $$ = $2;
}
| ICE_METADATA_OPEN error ICE_METADATA_CLOSE
{
    $$ = make_shared<MetadataListTok>();
}
| ICE_METADATA_OPEN ICE_METADATA_CLOSE
{
    currentUnit->warning(WarningCategory::All, "No directives were provided in metadata list");
    $$ = make_shared<MetadataListTok>();
}
;

// ----------------------------------------------------------------------
metadata
// ----------------------------------------------------------------------
: metadata_directives
{
}
| %empty
{
    $$ = make_shared<MetadataListTok>();
}
;

// ----------------------------------------------------------------------
metadata_directives
// ----------------------------------------------------------------------
: local_metadata
{
    $$ = $1;
}
| metadata_directives local_metadata
{
    auto metadata1 = dynamic_pointer_cast<MetadataListTok>($1);
    auto metadata2 = dynamic_pointer_cast<MetadataListTok>($2);
    metadata1->v.splice(metadata1->v.end(), std::move(metadata2->v));
    $$ = metadata1;
}
;

// ----------------------------------------------------------------------
definitions
// ----------------------------------------------------------------------
: definitions file_metadata
{
    auto metadata = dynamic_pointer_cast<MetadataListTok>($2);
    if (!metadata->v.empty())
    {
        currentUnit->addFileMetadata(std::move(metadata->v));
    }
}
| definitions metadata definition
{
    auto metadata = dynamic_pointer_cast<MetadataListTok>($2);
    auto contained = dynamic_pointer_cast<Contained>($3);
    if (contained && !metadata->v.empty())
    {
        contained->appendMetadata(std::move(metadata->v));
    }
}
| %empty
{
}
;

// ----------------------------------------------------------------------
definition
// ----------------------------------------------------------------------
: module_def opt_semicolon
{
    assert(dynamic_pointer_cast<Module>($1));
}
| class_decl ';'
{
    assert($1 == nullptr || dynamic_pointer_cast<ClassDecl>($1));
}
| class_def opt_semicolon
{
    assert($1 == nullptr || dynamic_pointer_cast<ClassDef>($1));
}
| interface_decl ';'
{
    assert($1 == nullptr || dynamic_pointer_cast<InterfaceDecl>($1));
}
| interface_def opt_semicolon
{
    assert($1 == nullptr || dynamic_pointer_cast<InterfaceDef>($1));
}
| exception_decl ';'
{
    assert($1 == nullptr);
}
| exception_def opt_semicolon
{
    assert(dynamic_pointer_cast<Exception>($1));
}
| struct_decl ';'
{
    assert($1 == nullptr);
}
| struct_def opt_semicolon
{
    assert(dynamic_pointer_cast<Struct>($1));
}
| sequence_def ';'
{
    assert(dynamic_pointer_cast<Sequence>($1));
}
| dictionary_def ';'
{
    assert(dynamic_pointer_cast<Dictionary>($1));
}
| enum_def opt_semicolon
{
    assert(dynamic_pointer_cast<Enum>($1));
}
| const_def ';'
{
    assert(dynamic_pointer_cast<Const>($1));
}
| error ';'
{
    yyerrok;
}
;

// ----------------------------------------------------------------------
module_def
// ----------------------------------------------------------------------
: ICE_MODULE definition_name
{
    currentUnit->setSeenDefinition();

    auto ident = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    ModulePtr module = cont->createModule(ident->v, false);

    cont->checkHasChangedMeaning(ident->v, module);
    currentUnit->pushContainer(module);
    $$ = module;
}
'{' definitions '}'
{
    currentUnit->popContainer();
    $$ = $3;
}
| ICE_MODULE ICE_SCOPED_IDENTIFIER
{
    currentUnit->setSeenDefinition();

    auto ident = dynamic_pointer_cast<StringTok>($2);

    // Reject scoped identifiers starting with "::". This generally indicates global scope, but is invalid here.
    size_t startPos = 0;
    if (ident->v.find("::") == 0)
    {
        currentUnit->error("illegal identifier: module identifiers cannot start with '::' prefix");
        startPos += 2; // Skip the leading "::".
    }

    // Split the scoped-identifier token into separate module names.
    size_t endPos;
    vector<string> modules;
    while ((endPos = ident->v.find("::", startPos)) != string::npos)
    {
        modules.push_back(ident->v.substr(startPos, (endPos - startPos)));
        startPos = endPos + 2; // Skip the "::" separator.
    }
    modules.push_back(ident->v.substr(startPos));

    // Create the nested modules.
    ContainerPtr cont = currentUnit->currentContainer();
    for (size_t i = 0; i < modules.size(); i++)
    {
        const auto currentModuleName = modules[i];
        ModulePtr module = cont->createModule(currentModuleName, true);

        cont->checkHasChangedMeaning(currentModuleName, module);
        currentUnit->pushContainer(module);
        $$ = cont = module;
    }
}
'{' definitions '}'
{
    // We need to pop '(N+1)' modules off the container stack, to navigate out of the nested module.
    // Where `N` is the number of scope separators ("::").
    size_t startPos = 0;
    auto ident = dynamic_pointer_cast<StringTok>($2);

    // Skip over any leading "::". This is invalid syntax of course, but the parser still needs to properly handle it.
    if (ident->v.find("::") == 0)
    {
        startPos += 2; // Skip the leading "::".
    }

    while ((startPos = ident->v.find("::", startPos)) != string::npos)
    {
        currentUnit->popContainer();
        startPos += 2; // Skip the "::" separator.
    }

    // Set the 'return value' to the outer-most module, before we pop it off the stack.
    // Whichever module we return, is the one that metadata will be applied to.
    $$ = currentUnit->currentContainer();
    currentUnit->popContainer();
}
;

// ----------------------------------------------------------------------
exception_decl
// ----------------------------------------------------------------------
: ICE_EXCEPTION definition_name
{
    currentUnit->error("exceptions cannot be forward declared");
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
exception_def
// ----------------------------------------------------------------------
: ICE_EXCEPTION definition_name exception_extends
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    auto base = dynamic_pointer_cast<Exception>($3);
    ContainerPtr cont = currentUnit->currentContainer();
    ExceptionPtr ex = cont->createException(ident->v, base);

    cont->checkHasChangedMeaning(ident->v, ex);
    currentUnit->pushContainer(ex);
    $$ = ex;
}
'{' data_members '}'
{
    currentUnit->popContainer();
    $$ = $4;
}
;

// ----------------------------------------------------------------------
exception_extends
// ----------------------------------------------------------------------
: extends scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    ContainedPtr contained = cont->lookupException(scoped->v, true);
    cont->checkHasChangedMeaning(scoped->v);
    $$ = contained;
}
| %empty
{
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
type_id
// ----------------------------------------------------------------------
: type definition_name
{
    auto type = dynamic_pointer_cast<Type>($1);
    auto ident = dynamic_pointer_cast<StringTok>($2);
    $$ = make_shared<TypeStringTok>(type, ident->v);
}
;

// ----------------------------------------------------------------------
optional
// ----------------------------------------------------------------------
: ICE_OPTIONAL_OPEN integer_constant ')'
{
    auto integer = dynamic_pointer_cast<IntegerTok>($2);
    int32_t tag = -1;

    if (integer && checkIntegerBounds(integer, "tag"))
    {
        tag = static_cast<int32_t>(integer->v);
    }

    auto m = make_shared<OptionalDefTok>(tag);
    $$ = m;
}
| ICE_OPTIONAL_OPEN ')'
{
    currentUnit->error("missing tag");
    auto m = make_shared<OptionalDefTok>(-1); // Dummy
    $$ = m;
}
| ICE_OPTIONAL
{
    currentUnit->error("missing tag");
    auto m = make_shared<OptionalDefTok>(-1); // Dummy
    $$ = m;
}
;

// ----------------------------------------------------------------------
optional_type_id
// ----------------------------------------------------------------------
: optional type_id
{
    auto m = dynamic_pointer_cast<OptionalDefTok>($1);
    auto ts = dynamic_pointer_cast<TypeStringTok>($2);
    m->type = ts->type;
    m->name = ts->name;

    // It's safe to perform this check in the parser, since we already have enough information to know whether a type
    // can be optional. This is because the only types that can be forward declared (classes/interfaces) have constant
    // values for `usesClasses` (true/false respectively).
    if (m->type && m->type->usesClasses())
    {
        currentUnit->error("types that use classes cannot be marked with 'optional'");
    }

    $$ = m;
}
| type_id
{
    auto ts = dynamic_pointer_cast<TypeStringTok>($1);
    auto m = make_shared<OptionalDefTok>(-1);
    m->type = ts->type;
    m->name = ts->name;
    $$ = m;
}
;

// ----------------------------------------------------------------------
struct_decl
// ----------------------------------------------------------------------
: ICE_STRUCT definition_name
{
    currentUnit->error("structs cannot be forward declared");
    $$ = nullptr; // Dummy
}
;

// ----------------------------------------------------------------------
struct_def
// ----------------------------------------------------------------------
: ICE_STRUCT definition_name
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    StructPtr st = cont->createStruct(ident->v);

    cont->checkHasChangedMeaning(ident->v, st);
    currentUnit->pushContainer(st);
    $$ = st;
}
'{' data_members '}'
{
    currentUnit->popContainer();

    // Empty structures are not allowed
    auto st = dynamic_pointer_cast<Struct>($3);
    if (st->dataMembers().empty())
    {
        currentUnit->error("struct '" + st->name() + "' must have at least one member");
    }
    $$ = st;
}
;

// ----------------------------------------------------------------------
class_name
// ----------------------------------------------------------------------
: ICE_CLASS ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_CLASS keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword '" + ident->v + "' cannot be used as class name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
class_id
// ----------------------------------------------------------------------
: ICE_CLASS definition_name_open integer_constant ')'
{
    auto integer = dynamic_pointer_cast<IntegerTok>($3);
    int32_t id = -1;

    if (integer && checkIntegerBounds(integer, "compact id"))
    {
        id = static_cast<int32_t>(integer->v);
        string typeId = currentUnit->getTypeId(id);
        if (!typeId.empty())
        {
            currentUnit->error("invalid compact id for class: already assigned to class '" + typeId + "'");
        }
    }

    auto classId = make_shared<ClassIdTok>();
    classId->v = dynamic_pointer_cast<StringTok>($2)->v;
    classId->t = id;
    $$ = classId;
}
| class_name
{
    auto classId = make_shared<ClassIdTok>();
    classId->v = dynamic_pointer_cast<StringTok>($1)->v;
    classId->t = -1;
    $$ = classId;
}
;

// ----------------------------------------------------------------------
class_decl
// ----------------------------------------------------------------------
: class_name
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    ClassDeclPtr cl = cont->createClassDecl(ident->v);
    $$ = cl;
}
;

// ----------------------------------------------------------------------
class_def
// ----------------------------------------------------------------------
: class_id class_extends
{
    auto ident = dynamic_pointer_cast<ClassIdTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    auto base = dynamic_pointer_cast<ClassDef>($2);
    ClassDefPtr cl = cont->createClassDef(ident->v, ident->t, base);
    if (cl)
    {
        cont->checkHasChangedMeaning(ident->v, cl);
        currentUnit->pushContainer(cl);
        $$ = cl;
    }
    else
    {
        $$ = nullptr;
    }
}
'{' data_members '}'
{
    if ($3)
    {
        currentUnit->popContainer();
        $$ = $3;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
class_extends
// ----------------------------------------------------------------------
: extends scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    TypeList types = cont->lookupType(scoped->v);
    $$ = nullptr;
    if (!types.empty())
    {
        auto cl = dynamic_pointer_cast<ClassDecl>(types.front());
        if (!cl)
        {
            currentUnit->error("'" + scoped->v + "' is not a class");
        }
        else
        {
            ClassDefPtr def = cl->definition();
            if (!def)
            {
                currentUnit->error("'" + scoped->v + "' has been declared but not defined");
            }
            else
            {
                cont->checkHasChangedMeaning(scoped->v);
                $$ = def;
            }
        }
    }
}
| %empty
{
    $$ = nullptr;
}
;

// ----------------------------------------------------------------------
extends
// ----------------------------------------------------------------------
: ICE_EXTENDS
{
}
| ':'
{
}
;

// ----------------------------------------------------------------------
data_members
// ----------------------------------------------------------------------
: metadata data_member ';' data_members
{
    auto metadata = dynamic_pointer_cast<MetadataListTok>($1);
    auto contained = dynamic_pointer_cast<Contained>($2);
    if (contained && !metadata->v.empty())
    {
        contained->appendMetadata(std::move(metadata->v));
    }
}
| error ';' data_members
{
}
| metadata data_member
{
    currentUnit->error("';' missing after definition");
}
| %empty
{
}
;

// ----------------------------------------------------------------------
data_member
// ----------------------------------------------------------------------
: optional_type_id
{
    auto def = dynamic_pointer_cast<OptionalDefTok>($1);
    DataMemberPtr dm;

    if (auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer()))
    {
        dm = cl->createDataMember(def->name, def->type, def->isOptional, def->tag, nullptr, std::nullopt);
    }
    else if (auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer()))
    {
        if (def->isOptional)
        {
            currentUnit->error("optional data members are not supported in structs");
        }
        dm = st->createDataMember(def->name, def->type, nullptr, std::nullopt);
    }
    else if (auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer()))
    {
        dm = ex->createDataMember(def->name, def->type, def->isOptional, def->tag, nullptr, std::nullopt);
    }

    if (dm)
    {
        currentUnit->currentContainer()->checkHasChangedMeaning(def->name, dm);
    }
    $$ = dm;
}
| optional_type_id '=' const_initializer
{
    auto def = dynamic_pointer_cast<OptionalDefTok>($1);
    auto value = dynamic_pointer_cast<ConstDefTok>($3);
    DataMemberPtr dm;

    if (auto cl = dynamic_pointer_cast<ClassDef>(currentUnit->currentContainer()))
    {
        dm = cl->createDataMember(def->name, def->type, def->isOptional, def->tag, value->v, value->valueAsString);
    }
    else if (auto st = dynamic_pointer_cast<Struct>(currentUnit->currentContainer()))
    {
        if (def->isOptional)
        {
            currentUnit->error("optional data members are not supported in structs");
        }
        dm = st->createDataMember(def->name, def->type, value->v, value->valueAsString);
    }
    else if (auto ex = dynamic_pointer_cast<Exception>(currentUnit->currentContainer()))
    {
        dm = ex->createDataMember(def->name, def->type, def->isOptional, def->tag, value->v, value->valueAsString);
    }

    if (dm)
    {
        currentUnit->currentContainer()->checkHasChangedMeaning(def->name, dm);
    }
    $$ = dm;
}
;

// ----------------------------------------------------------------------
return_type
// ----------------------------------------------------------------------
: optional type
{
    auto m = dynamic_pointer_cast<OptionalDefTok>($1);
    m->type = dynamic_pointer_cast<Type>($2);

    // It's safe to perform this check in the parser, since we already have enough information to know whether a type
    // can be optional. This is because the only types that can be forward declared (classes/interfaces) have constant
    // values for `usesClasses` (true/false respectively).
    if (m->type && m->type->usesClasses())
    {
        currentUnit->error("types that use classes cannot be marked with 'optional'");
    }

    $$ = m;
}
| type
{
    auto m = make_shared<OptionalDefTok>(-1);
    m->type = dynamic_pointer_cast<Type>($1);
    $$ = m;
}
| ICE_VOID
{
    auto m = make_shared<OptionalDefTok>(-1);
    $$ = m;
}
;

// ----------------------------------------------------------------------
idempotent_modifier
// ----------------------------------------------------------------------
: ICE_IDEMPOTENT
{
    $$ = make_shared<BoolTok>(true);
}
| %empty
{
    $$ = make_shared<BoolTok>(false);
}
;

// ----------------------------------------------------------------------
operation_preamble
// ----------------------------------------------------------------------
: idempotent_modifier return_type definition_name_open
{
    bool isIdempotent = dynamic_pointer_cast<BoolTok>($1)->v;
    auto returnType = dynamic_pointer_cast<OptionalDefTok>($2);
    string name = dynamic_pointer_cast<StringTok>($3)->v;
    auto interface = dynamic_pointer_cast<InterfaceDef>(currentUnit->currentContainer());
    if (interface)
    {
        OperationPtr op = interface->createOperation(
            name,
            returnType->type,
            returnType->isOptional,
            returnType->tag,
            isIdempotent ? Operation::Idempotent : Operation::Normal);

        interface->checkHasChangedMeaning(name, op);
        currentUnit->pushContainer(op);
        $$ = op;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
operation
// ----------------------------------------------------------------------
: operation_preamble parameters ')'
{
    if ($1)
    {
        // Check that all out parameters come before all in parameters.
        auto op = dynamic_pointer_cast<Operation>($1);
        bool seenOutParam = false;
        for (const auto& param : op->parameters())
        {
            const bool isOutParam = param->isOutParam();
            if (!isOutParam && seenOutParam)
            {
                currentUnit->error("parameter '" + param->name() + "': in-parameters cannot come after out-parameters");
            }
            seenOutParam |= isOutParam;
        }

        currentUnit->popContainer();
    }
    $$ = $1;
}
throws
{
    auto op = dynamic_pointer_cast<Operation>($4);
    auto el = dynamic_pointer_cast<ExceptionListTok>($5);
    assert(el);
    if (op)
    {
        op->setExceptionList(el->v);
    }
}
| operation_preamble error ')'
{
    if ($1)
    {
        currentUnit->popContainer();
    }
    yyerrok;
}
throws
{
    auto op = dynamic_pointer_cast<Operation>($4);
    auto el = dynamic_pointer_cast<ExceptionListTok>($5);
    assert(el);
    if (op)
    {
        op->setExceptionList(el->v); // Dummy
    }
}
;

// ----------------------------------------------------------------------
interface_id
// ----------------------------------------------------------------------
: ICE_INTERFACE ICE_IDENTIFIER
{
    $$ = $2;
}
| ICE_INTERFACE keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    currentUnit->error("keyword '" + ident->v + "' cannot be used as interface name");
    $$ = $2; // Dummy
}
;

// ----------------------------------------------------------------------
interface_decl
// ----------------------------------------------------------------------
: interface_id
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    auto cont = currentUnit->currentContainer();
    InterfaceDeclPtr cl = cont->createInterfaceDecl(ident->v);
    cont->checkHasChangedMeaning(ident->v, cl);
    $$ = cl;
}
;

// ----------------------------------------------------------------------
interface_def
// ----------------------------------------------------------------------
: interface_id interface_extends
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    auto bases = dynamic_pointer_cast<InterfaceListTok>($2);
    InterfaceDefPtr interface = cont->createInterfaceDef(ident->v, bases->v);
    if (interface)
    {
        cont->checkHasChangedMeaning(ident->v, interface);
        currentUnit->pushContainer(interface);
        $$ = interface;
    }
    else
    {
        $$ = nullptr;
    }
}
'{' operations '}'
{
    if ($3)
    {
        currentUnit->popContainer();
        $$ = $3;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
interface_list
// ----------------------------------------------------------------------
: interface_list ',' scoped_name
{
    auto interfaces = dynamic_pointer_cast<InterfaceListTok>($1);
    auto scoped = dynamic_pointer_cast<StringTok>($3);
    if (auto interfaceDef = lookupInterfaceByName(scoped->v))
    {
        interfaces->v.push_back(interfaceDef);
    }
    $$ = interfaces;
}
| scoped_name
{
    auto interfaces = make_shared<InterfaceListTok>();
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    if (auto interfaceDef = lookupInterfaceByName(scoped->v))
    {
        interfaces->v.push_back(interfaceDef);
    }
    $$ = interfaces;
}
| ICE_OBJECT
{
    currentUnit->error("illegal inheritance from type Object");
    $$ = make_shared<InterfaceListTok>(); // Dummy
}
| ICE_VALUE
{
    currentUnit->error("illegal inheritance from type Value");
    $$ = make_shared<InterfaceListTok>(); // Dummy
}
;

// ----------------------------------------------------------------------
interface_extends
// ----------------------------------------------------------------------
: extends interface_list
{
    $$ = $2;
}
| %empty
{
    $$ = make_shared<InterfaceListTok>();
}
;

// ----------------------------------------------------------------------
operations
// ----------------------------------------------------------------------
: metadata operation ';' operations
{
    auto metadata = dynamic_pointer_cast<MetadataListTok>($1);
    auto contained = dynamic_pointer_cast<Contained>($2);
    if (contained && !metadata->v.empty())
    {
        contained->appendMetadata(std::move(metadata->v));
    }
}
| error ';' operations
{
}
| metadata operation
{
    currentUnit->error("';' missing after definition");
}
| %empty
{
}
;

// ----------------------------------------------------------------------
exception_list
// ----------------------------------------------------------------------
: exception_list ',' exception
{
    auto exceptionList = dynamic_pointer_cast<ExceptionListTok>($1);
    auto exception = dynamic_pointer_cast<Exception>($3);
    if (exception)
    {
        exceptionList->v.push_back(exception);
    }
    $$ = exceptionList;
}
| exception
{
    auto exceptionList = make_shared<ExceptionListTok>();
    auto exception = dynamic_pointer_cast<Exception>($1);
    if (exception)
    {
        exceptionList->v.push_back(exception);
    }
    $$ = exceptionList;
}
;

// ----------------------------------------------------------------------
exception
// ----------------------------------------------------------------------
: scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    ExceptionPtr exception = cont->lookupException(scoped->v, true);
    if (exception)
    {
        cont->checkHasChangedMeaning(scoped->v, exception);
    }
    $$ = exception;
}
| keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    currentUnit->error("keyword '" + ident->v + "' cannot be used as a name");
    ContainerPtr cont = currentUnit->currentContainer();
    ExceptionPtr exception = cont->lookupException(ident->v, false);
    if (exception)
    {
        cont->checkHasChangedMeaning(ident->v, exception);
    }
    $$ = exception;
}
;

// ----------------------------------------------------------------------
sequence_def
// ----------------------------------------------------------------------
: ICE_SEQUENCE '<' metadata type '>' definition_name
{
    auto ident = dynamic_pointer_cast<StringTok>($6);
    auto metadata = dynamic_pointer_cast<MetadataListTok>($3);
    auto type = dynamic_pointer_cast<Type>($4);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createSequence(ident->v, type, std::move(metadata->v));
}
;

// ----------------------------------------------------------------------
dictionary_def
// ----------------------------------------------------------------------
: ICE_DICTIONARY '<' metadata type ',' metadata type '>' definition_name
{
    auto ident = dynamic_pointer_cast<StringTok>($9);
    auto keyMetadata = dynamic_pointer_cast<MetadataListTok>($3);
    auto keyType = dynamic_pointer_cast<Type>($4);
    auto valueMetadata = dynamic_pointer_cast<MetadataListTok>($6);
    auto valueType = dynamic_pointer_cast<Type>($7);
    ContainerPtr cont = currentUnit->currentContainer();
    $$ = cont->createDictionary(ident->v, keyType, std::move(keyMetadata->v), valueType, std::move(valueMetadata->v));
}
;

// ----------------------------------------------------------------------
enum_def
// ----------------------------------------------------------------------
: ICE_ENUM definition_name
{
    auto ident = dynamic_pointer_cast<StringTok>($2);
    ContainerPtr cont = currentUnit->currentContainer();
    EnumPtr en = cont->createEnum(ident->v);
    cont->checkHasChangedMeaning(ident->v, en);
    currentUnit->pushContainer(en);
    $$ = en;
}
'{' enumerators '}'
{
    auto en = dynamic_pointer_cast<Enum>($3);
    auto enumerators = dynamic_pointer_cast<EnumeratorListTok>($5);
    if (enumerators->v.empty())
    {
        currentUnit->error("enum '" + en->name() + "' must have at least one enumerator");
    }
    currentUnit->popContainer();
    $$ = en;
}
;

// ----------------------------------------------------------------------
enumerators
// ----------------------------------------------------------------------
: enumerator_list
| enumerator_list ','
{
    $$ = $1;
}
| %empty
{
    $$ = make_shared<EnumeratorListTok>(); // Empty list
}
;

// ----------------------------------------------------------------------
enumerator_list
// ----------------------------------------------------------------------
: enumerator_list ',' metadata enumerator
{
    auto enumeratorList = dynamic_pointer_cast<EnumeratorListTok>($1);
    auto metadata = dynamic_pointer_cast<MetadataListTok>($3);
    auto enumerator = dynamic_pointer_cast<Enumerator>($4);
    if (enumerator && !metadata->v.empty())
    {
        enumerator->appendMetadata(std::move(metadata->v));
    }
    enumeratorList->v.push_back(enumerator);
    $$ = enumeratorList;
}
| metadata enumerator
{
    auto enumeratorList = make_shared<EnumeratorListTok>();
    auto metadata = dynamic_pointer_cast<MetadataListTok>($1);
    auto enumerator = dynamic_pointer_cast<Enumerator>($2);
    if (enumerator && !metadata->v.empty())
    {
        enumerator->appendMetadata(std::move(metadata->v));
    }
    enumeratorList->v.push_back(enumerator);
    $$ = enumeratorList;
}
;

// ----------------------------------------------------------------------
enumerator
// ----------------------------------------------------------------------
: ICE_IDENTIFIER
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    $$ = cont->createEnumerator(ident->v, nullopt);
}
| ICE_IDENTIFIER '=' integer_constant
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    auto intVal = dynamic_pointer_cast<IntegerTok>($3);
    if (intVal)
    {
        // We report numbers that are out of range, but always create the enumerator no matter what.
        checkIntegerBounds(intVal, "enumerator value");
        $$ = cont->createEnumerator(ident->v, static_cast<int32_t>(intVal->v));
    }
    else
    {
        $$ = cont->createEnumerator(ident->v, nullopt); // Dummy
    }
}
| keyword
{
    auto ident = dynamic_pointer_cast<StringTok>($1);
    EnumPtr cont = dynamic_pointer_cast<Enum>(currentUnit->currentContainer());
    currentUnit->error("keyword '" + ident->v + "' cannot be used as enumerator");
    $$ = cont->createEnumerator(ident->v, nullopt); // Dummy
}
;

// ----------------------------------------------------------------------
parameter
// ----------------------------------------------------------------------
: optional_type_id
{
    auto tsp = dynamic_pointer_cast<OptionalDefTok>($1);
    ParameterPtr param;

    auto op = dynamic_pointer_cast<Operation>(currentUnit->currentContainer());
    if (op)
    {
        param = op->createParameter(tsp->name, tsp->type, tsp->isOptional, tsp->tag);
        currentUnit->currentContainer()->checkHasChangedMeaning(tsp->name, param);
    }
    $$ = param;
}
| ICE_OUT parameter
{
    if (auto param = dynamic_pointer_cast<Parameter>($2))
    {
        param->setIsOutParam();
    }
    $$ = $2;
}
| local_metadata parameter
{
    if (auto param = dynamic_pointer_cast<Parameter>($2))
    {
        auto metadata = dynamic_pointer_cast<MetadataListTok>($1);
        param->appendMetadata(std::move(metadata->v));
    }
    $$ = $2;
}
;

// ----------------------------------------------------------------------
parameters
// ----------------------------------------------------------------------
: parameter_list
| %empty
;

// ----------------------------------------------------------------------
parameter_list
// ----------------------------------------------------------------------
: parameter
| parameter_list ',' parameter
;

// ----------------------------------------------------------------------
throws
// ----------------------------------------------------------------------
: ICE_THROWS exception_list
{
    $$ = $2;
}
| %empty
{
    $$ = make_shared<ExceptionListTok>();
}
;

// ----------------------------------------------------------------------
scoped_name
// ----------------------------------------------------------------------
: ICE_IDENTIFIER
{
}
| ICE_SCOPED_IDENTIFIER
{
}
;

// ----------------------------------------------------------------------
builtin
// ----------------------------------------------------------------------
: ICE_BOOL {}
| ICE_BYTE {}
| ICE_SHORT {}
| ICE_INT {}
| ICE_LONG {}
| ICE_FLOAT {}
| ICE_DOUBLE {}
| ICE_STRING {}
| ICE_OBJECT {}
| ICE_VALUE {}

// ----------------------------------------------------------------------
type
// ----------------------------------------------------------------------
: ICE_OBJECT '*'
{
    $$ = currentUnit->createBuiltin(Builtin::KindObjectProxy);
}
| builtin
{
    auto typeName = dynamic_pointer_cast<StringTok>($1);
    $$ = currentUnit->createBuiltin(Builtin::kindFromString(typeName->v).value());
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    $$ = lookupTypeByName(scoped->v, false);
}
| scoped_name '*'
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    $$ = lookupTypeByName(scoped->v, true);
}
;

// ----------------------------------------------------------------------
integer_constant
// ----------------------------------------------------------------------
: ICE_INTEGER_LITERAL
{
    $$ = $1;
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ContainerPtr cont = currentUnit->currentContainer();
    ContainedList cl = cont->lookupContained(scoped->v, false);

    if (cl.empty())
    {
        EnumeratorList enumerators = cont->enumerators(scoped->v);
        if (enumerators.size() == 1)
        {
            // We found the enumerator the user must of been referencing.
            cl.push_back(enumerators.front());
            scoped->v = enumerators.front()->scoped();
        }
        else if (enumerators.size() > 1)
        {
            // There are multiple enumerators and it's ambiguous which to use.
            bool first = true;
            ostringstream os;
            os << "enumerator '" << scoped->v << "' could designate";
            for (const auto& p : enumerators)
            {
                if (!first)
                {
                    os << " or";
                }
                first = false;

                os << " '" << p->scoped() << "'";
            }
            currentUnit->error(os.str());

            // Use the first enumerator we found, so parsing can continue.
            cl.push_back(enumerators.front());
        }
    }

    optional<int64_t> integerValue;
    if (cl.empty())
    {
        // If we couldn't find any Slice types matching the provided name, report an error.
        currentUnit->error("'" + scoped->v + "' is not defined");
    }
    else
    {
        cont->checkHasChangedMeaning(scoped->v);
        if (auto constant = dynamic_pointer_cast<Const>(cl.front()))
        {
            auto b = dynamic_pointer_cast<Builtin>(constant->type());
            if (b && b->isIntegralType())
            {
                integerValue = std::stoll(constant->value(), nullptr, 0);
            }
        }
        else if (auto enumerator = dynamic_pointer_cast<Enumerator>(cl.front()))
        {
            integerValue = enumerator->value();
        }

        // If the provided name resolved to a non-integer-constant Slice type, report an error.
        if (!integerValue)
        {
            currentUnit->error(cl.front()->kindOf() + " '" + scoped->v + "' cannot be used as an integer constant");
        }
    }

    if (integerValue)
    {
        // Return the value that we resolved.
        auto tok = make_shared<IntegerTok>();
        tok->v = *integerValue;
        tok->literal = scoped->v;
        $$ = tok;
    }
    else
    {
        $$ = nullptr;
    }
}
;

// ----------------------------------------------------------------------
string_literal
// ----------------------------------------------------------------------
: ICE_STRING_LITERAL string_literal // Adjacent string literals are concatenated
{
    auto str1 = dynamic_pointer_cast<StringTok>($1);
    auto str2 = dynamic_pointer_cast<StringTok>($2);
    str1->v += str2->v;
}
| ICE_STRING_LITERAL
{
}
;

// ----------------------------------------------------------------------
metadata_list
// ----------------------------------------------------------------------
: metadata_list ',' string_literal
{
    auto str = dynamic_pointer_cast<StringTok>($3);
    auto metadataList = dynamic_pointer_cast<MetadataListTok>($1);

    auto metadata = make_shared<Metadata>(str->v, currentUnit->currentFile(), currentUnit->currentLine());
    metadataList->v.push_back(metadata);

    $$ = metadataList;
}
| string_literal
{
    auto str = dynamic_pointer_cast<StringTok>($1);
    auto metadataList = make_shared<MetadataListTok>();

    auto metadata = make_shared<Metadata>(str->v, currentUnit->currentFile(), currentUnit->currentLine());
    metadataList->v.push_back(metadata);

    $$ = metadataList;
}
;

// ----------------------------------------------------------------------
const_initializer
// ----------------------------------------------------------------------
: ICE_INTEGER_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindLong);
    auto intVal = dynamic_pointer_cast<IntegerTok>($1);
    ostringstream sstr;
    sstr << intVal->v;
    auto def = make_shared<ConstDefTok>(type, sstr.str());
    $$ = def;
}
| ICE_FLOATING_POINT_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindDouble);
    auto floatVal = dynamic_pointer_cast<FloatingTok>($1);
    ostringstream sstr;
    sstr << floatVal->v;
    auto def = make_shared<ConstDefTok>(type, sstr.str());
    $$ = def;
}
| scoped_name
{
    auto scoped = dynamic_pointer_cast<StringTok>($1);
    ConstDefTokPtr def;
    ContainedList cl = currentUnit->currentContainer()->lookupContained(scoped->v, false);
    if (cl.empty())
    {
        // Could be an enumerator
        def = make_shared<ConstDefTok>(nullptr, scoped->v);
    }
    else
    {
        auto enumerator = dynamic_pointer_cast<Enumerator>(cl.front());
        auto constant = dynamic_pointer_cast<Const>(cl.front());
        if (enumerator)
        {
            currentUnit->currentContainer()->checkHasChangedMeaning(scoped->v, enumerator);
            def = make_shared<ConstDefTok>(enumerator, scoped->v);
        }
        else if (constant)
        {
            currentUnit->currentContainer()->checkHasChangedMeaning(scoped->v, constant);
            def = make_shared<ConstDefTok>(constant, constant->value());
        }
        else
        {
            def = make_shared<ConstDefTok>();
            string msg = "illegal initializer: '" + scoped->v + "' is ";
            string kindOf = cl.front()->kindOf();
            msg += getArticleFor(kindOf) + " " + kindOf;
            currentUnit->error(msg); // $$ is dummy
        }
    }
    $$ = def;
}
| ICE_STRING_LITERAL
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindString);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, literal->v);
    $$ = def;
}
| ICE_FALSE
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindBool);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, "false");
    $$ = def;
}
| ICE_TRUE
{
    BuiltinPtr type = currentUnit->createBuiltin(Builtin::KindBool);
    auto literal = dynamic_pointer_cast<StringTok>($1);
    auto def = make_shared<ConstDefTok>(type, "true");
    $$ = def;
}
;

// ----------------------------------------------------------------------
const_def
// ----------------------------------------------------------------------
: ICE_CONST metadata type definition_name '=' const_initializer
{
    auto metadata = dynamic_pointer_cast<MetadataListTok>($2);
    auto const_type = dynamic_pointer_cast<Type>($3);
    auto ident = dynamic_pointer_cast<StringTok>($4);
    auto value = dynamic_pointer_cast<ConstDefTok>($6);
    $$ = currentUnit->currentContainer()->createConst(ident->v, const_type, std::move(metadata->v), value->v,
                                                      value->valueAsString);
}
;

// ----------------------------------------------------------------------
definition_name
// ----------------------------------------------------------------------
: ICE_IDENTIFIER
{
    // All good, this is a valid identifier.
    $$ = $1;
}
| keyword
{
    // If an un-escaped keyword was used as an identifier, we emit an error,
    // but continue along, pretending like the user escaped the keyword.
    auto ident = dynamic_pointer_cast<StringTok>($1);
    currentUnit->error("keyword '" + ident->v + "' cannot be used as a name");
    $$ = ident;
}
| %empty %prec EMPTY_PREC
{
    // If the user forgot to give a name to a Slice definition, we emit an error,
    // but continue along, returning an empty string instead of an identifier.
    currentUnit->error("missing name");
    $$ = make_shared<StringTok>();
}
;

// ----------------------------------------------------------------------
definition_name_open
// ----------------------------------------------------------------------
: ICE_IDENT_OPEN
{
    // All good, this is a valid identifier.
    $$ = $1;
}
| ICE_KEYWORD_OPEN
{
    // If an un-escaped keyword was used as an identifier, we emit an error,
    // but continue along, pretending like the user escaped the keyword.
    auto ident = dynamic_pointer_cast<StringTok>($1);
    currentUnit->error("keyword '" + ident->v + "' cannot be used as a name");
    $$ = ident;
}
;

// ----------------------------------------------------------------------
keyword
// ----------------------------------------------------------------------
: ICE_MODULE {}
| ICE_CLASS {}
| ICE_INTERFACE {}
| ICE_EXCEPTION {}
| ICE_STRUCT {}
| ICE_SEQUENCE {}
| ICE_DICTIONARY {}
| ICE_ENUM {}
| ICE_OUT {}
| ICE_EXTENDS {}
| ICE_THROWS {}
| ICE_VOID {}
| ICE_BOOL {}
| ICE_BYTE {}
| ICE_SHORT {}
| ICE_INT {}
| ICE_LONG {}
| ICE_FLOAT {}
| ICE_DOUBLE {}
| ICE_STRING {}
| ICE_OBJECT {}
| ICE_CONST {}
| ICE_FALSE {}
| ICE_TRUE {}
| ICE_IDEMPOTENT {}
| ICE_OPTIONAL {}
| ICE_VALUE {}
;

%%

// NOLINTEND

namespace
{
    TypePtr lookupTypeByName(const string& name, bool expectInterfaceType)
    {
        ContainerPtr cont = currentUnit->currentContainer();
        TypeList types = cont->lookupType(name);
        if (types.empty())
        {
            return nullptr;
        }

        TypePtr firstType = types.front();
        auto interface = dynamic_pointer_cast<InterfaceDecl>(firstType);
        if (interface && !expectInterfaceType)
        {
            string msg = "add a '*' after the interface name to specify its proxy type: '" + name + "*'";
            currentUnit->error(msg);
        }
        if (!interface && expectInterfaceType)
        {
            string msg = "'" + name + "' must be an interface";
            currentUnit->error(msg);
        }

        cont->checkHasChangedMeaning(name);
        return firstType;
    }

    InterfaceDefPtr lookupInterfaceByName(const string& name)
    {
        ContainerPtr cont = currentUnit->currentContainer();
        InterfaceDefPtr interfaceDef = cont->lookupInterfaceDef(name, true);
        if (interfaceDef)
        {
            cont->checkHasChangedMeaning(name);
        }
        return interfaceDef;
    }

    bool checkIntegerBounds(const IntegerTokPtr& token, string_view kindString)
    {
        const int32_t max_value = std::numeric_limits<int32_t>::max();
        optional<string> errorReason;

        // Check if the integer is between the allowed bounds.
        if (token->v < 0)
        {
            errorReason = "cannot be negative";
        }
        else if (token->v > max_value)
        {
            errorReason = "must be less than or equal to '" + std::to_string(max_value) + "'";
        }

        // Report an error if the integer wasn't within bounds.
        if (errorReason)
        {
            ostringstream msg;
            msg << "invalid " << kindString << " '" << token->literal << "'";
            if (isalpha(token->literal[0]))
            {
                msg << " (" << token->v << ")";
            }
            msg << ": " << kindString << "s " << *errorReason;
            currentUnit->error(msg.str());
            return false;
        }
        else
        {
            return true;
        }
    }
}
